<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport"
        content="width=device-width, initial-scale=1.0,minimum-scale=1,maximum-scale=1,user-scalable=no">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <!-- <link rel="stylesheet" type="text/css" href="css/index.css">
    <script type="text/javascript" src="js/index.js"></script>
    <style type="text/css"></style> -->
    <title>Document</title>
    <style>
        div {
            width: 100px;
            height: 100px;
            background-color: red;
            position: absolute;
        }

        .box2 {
            top: 200px;
        }
    </style>
</head>

<body>
    <div class="box1"></div>
    <div class="box2"></div>
    <script>
        /**
         * @param element 待添加运动动画效果的DOM元素
         * @param options 存放要实现动画效果的属性以及该属性的最终值的对象
         * @param duration 限定运动的总时长
         * @param fn 可选参数，动画执行完后需要调用的函数
         */
        function animate(element, options, duration, fn) {
            //首先停止运动动画效果的定时器
            clearInterval(element.timer) //element.timer表示将定时器绑定到元素身上，当有多个定时器的时候，不同的元素就有自己专属的定时器不会导致冲突
            //初始化一个空的对象来存放元素各种属性的初始值
            const start = {}
            //初始化一个空的对象来存放元素个属性的变化速度
            const speed = {}
            //循环遍历options
            for (const key in options) {
                //元素的初始状态，因为有多个元素需要变化，所以循环遍历options拿到每个需要遍历的属性，然后在外部样式中拿到对应元素对应属性的初始值
                start[key] = parseFloat(window.getComputedStyle(element, null)[key])
                //元素的变化的动画速度，因为对象中有多个要变化的值，需要循环遍历对象分别计算拿到他们的动画速度
                speed[key] = (options[key] - start[key]) / duration
            }
            //拿到定时器启动前的时间
            const startTime = Date.now()
            //运动一次的方法
            function move() {
                //获取元素已经运动了多少时间
                const elapsed = Math.min(duration, Date.now() -
                    startTime) //因为定时器是每1000/60毫秒执行一次，所以定时器结束时Date.now()可能会大于总时长，所以这里取较小的那一个值
                for (const key in options) {
                    //拿到元素最终的状态
                    const result = elapsed * speed[key] + start[key]
                    //设置元素的样式
                    element.style[key] = result + (key === 'opacity' ? '' : 'px') //判断拿到的属性名是不是opacity，如果是就去掉px单位
                }
                //判断定时器启动的条件
                if (elapsed !== duration) {
                    //因为setTimeout方法只能执行一次所以使用递归调用move()实现重复启动定时器达到运动的效果
                    element.timer = setTimeout(move, 1000 / 60) //1000/60表示1秒60帧的画面
                } else {
                    //动画结束后，如果有要执行的任务，就调用函数继续执行
                    fn && fn()
                }

            }
            //执行move(),实现运动效果
            move()
        }
        document.querySelector('.box1').addEventListener('mouseenter', function (e) {
            animate(e.target, {
                width: 500,
                height: 500,
                left: 500,
                top: 100,
                opacity: 0.1
            }, 1000)
        }, false)
        document.querySelector('.box1').addEventListener('click', function (e) {
            animate(e.target, {
                width: 100,
                height: 100,
                left: 0,
                top: 0,
                opacity: 1
            }, 1000)
        }, false)
        document.querySelector('.box2').addEventListener('click', function (e) {
            animate(e.target, {
                width: 200
            }, 1000, function () {
                animate(e.target, {
                    height: 200
                }, 1000, function () {
                    animate(e.target, {
                        top: 300
                    }, 1000, function () {
                        animate(e.target, {
                            left: 300,
                            opacity: 0
                        }, 1000, function () {
                            e.target.style.display = 'none'
                        })
                    })
                })
            })
        }, false)
    </script>
</body>

</html>